/*
 * Famon (http://code.google.com/p/famon/)
 * Copyright (c) 2010 Carl Crous.
 *
 * This file is part of Famon.
 *
 * Famon is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Famon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Famon.  If not, see <http://www.gnu.org/licenses/>.
 */

package za.org.foobar.famon.webapp;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import za.org.foobar.famon.model.*;
import za.org.foobar.famon.util.DB;
import za.org.foobar.famon.util.DBQuery;

import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public class ApplicationLogic {
    private final Log log = LogFactory.getLog(getClass());

    private UserLogic userLogic = UserLogic.get();

    private static ApplicationLogic instance;
    public static ApplicationLogic get() {
        return instance == null ? instance = new ApplicationLogic() : instance;
    }

    public ApplicationLogic() {
        log.debug("Creating ApplicationLogic");
    }

    public boolean userHasRole(Application app, UserData user, int role) {
        if ((!app.isPrivate() && role == RoleEnum.user()) || userLogic.isUserAdmin()) {
            return true;
        }
        if (user == null) {
            return false;
        }
        List<ApplicationUser> users = app.getUserList();
        if (users != null) {
            Key userId = user.getId();
            for (ApplicationUser appUser : users) {
                if (userId.equals(appUser.getUser().getId())) {
                    return RoleEnum.hasRole(appUser.getRole(), role);
                }
            }
        }
        return false;
    }

    public boolean canViewApplication(Application app) {
        return canViewApplication(app, userLogic.getCurrentUser());
    }

    public boolean canViewApplication(Application app, UserData user) {
        return userHasRole(app, user, RoleEnum.USER);
    }

    public boolean canEditApplication(Application app) {
        return canEditApplication(app, userLogic.getCurrentUser());
    }
    
    public boolean canEditApplication(Application app, UserData user) {
        return userHasRole(app, user, RoleEnum.ADMIN);
    }

    public List<Application> getApplicationList() {
        DBQuery<Application> q = DB.createDBQuery(Application.class).addOrderBy("name");
        if (!userLogic.isUserLoggedIn()) {
            return q.addWhere("isPrivate", false).getResultList();
        }

        UserData user = userLogic.getCurrentUser();
        List<Application> list = q.getResultList();
        List<Application> authorisedList = new ArrayList<Application>();
        for (Application app : list) {
            if (canViewApplication(app, user)) {
                authorisedList.add(app);
            }
        }
        return authorisedList;
    }

    public Application getApplication(Key id) {
        Application app = DB.find(Application.class, id);
        if (app == null) {
            return null;
        }
        checkViewApplication(app);
        return app;
    }

    public void checkViewApplication(Application app) {
        if (!canViewApplication(app)) {
            throw new FamonSecurityException("User cannot view Application \"" + KeyFactory.keyToString(app.getId()) + "\"");
        }
    }

    public Application getApplicationForEdit(Key id) {
        Application app = DB.find(Application.class, id);
        if (app == null) {
            return null;
        }
        checkApplicationForEdit(app);
        return app;
    }

    public void checkApplicationForEdit(Application app) {
        if (!canEditApplication(app)) {
            throw new FamonSecurityException("User cannot edit Application \"" + KeyFactory.keyToString(app.getId()) + "\"");
        }
    }

//    public ApplicationDto getApplicationDto(Key id) {
//        return createApplicationDto(getApplication(id));
//    }
//
//    public List<ApplicationDto> getApplicationDtoList() {
//        List<Application> list = getApplicationList();
//        List<ApplicationDto> dtos = new ArrayList<ApplicationDto>();
//        for (Application app : list) {
//            dtos.add(createApplicationDto(app));
//        }
//        return dtos;
//    }

    public ApplicationVersion getApplicationVersion(Key id) {
        ApplicationVersion version = DB.find(ApplicationVersion.class, id);
        if (!canViewApplication(version.getApplication())) {
            throw new FamonSecurityException("User cannot view ApplicationVersion \"" + id + "\"");
        }
        return version;
    }

//    public ApplicationVersionDto getApplicationVersionDto(Key id) {
//        return createApplicationVersionDto(getApplicationVersion(id));
//    }

    public List<ApplicationVersion> getApplicationVersionList(Key applicationId) {
        // Checks the user can access the application
        getApplication(applicationId);
        return DB.createDBQuery(ApplicationVersion.class)
                .addWhere("applicationId == applicationIdParam")
                .addParameter("applicationIdParam", applicationId)
                .addOrderBy("name asc")
                .getResultList();
    }

//    public List<ApplicationVersionDto> getApplicationVersionDtoList(String applicationId) {
//        return getApplicationVersionDtoList(KeyFactory.stringToKey(applicationId));
//    }

//    public List<ApplicationVersionDto> getApplicationVersionDtoList(Key applicationId) {
//        List<ApplicationVersion> list = getApplicationVersionList(applicationId);
//        List<ApplicationVersionDto> dtos = new ArrayList<ApplicationVersionDto>();
//        for (ApplicationVersion app : list) {
//            dtos.add(createApplicationVersionDto(app));
//        }
//        return dtos;
//    }

    public ApplicationInstance getApplicationInstance(Key id) {
        ApplicationInstance instance = DB.find(ApplicationInstance.class, id);
        if (!canViewApplication(instance.getApplication())) {
            throw new FamonSecurityException("User cannot access ApplicationInstance \"" + id + "\"");
        }
        return instance;
    }

//    public ApplicationInstanceDto getApplicationInstanceDto(Key id) {
//        return createApplicationInstanceDto(getApplicationInstance(id));
//    }

//    public List<ApplicationInstanceDto> getApplicationInstanceDtoList(String applicationId) {
//        return getApplicationInstanceDtoList(KeyFactory.stringToKey(applicationId));
//    }

//    public List<ApplicationInstanceDto> getApplicationInstanceDtoList(Key applicationId) {
//        // Performs security checks
//        Application application = getApplication(applicationId);
//        List<ApplicationInstance> list = application.getInstanceList();
//        List<ApplicationInstanceDto> dtos = new ArrayList<ApplicationInstanceDto>();
//        for (ApplicationInstance app : list) {
//            dtos.add(createApplicationInstanceDto(app));
//        }
//        return dtos;
//    }

//    public ApplicationDto createApplicationDto(Application app) {
//        if (app == null) return null;
//        ApplicationDto dto = new ApplicationDto();
//        dto.id = app.keyToString();
//        dto.name = app.getName();
//        dto.isPrivate = app.isPrivate();
//        return dto;
//    }
//
//    public Application updateApplication(ApplicationDto dto) {
//        Application app = getApplicationForEdit(Application.stringToKey(dto.id));
//        if (app == null) {
//            return null;
//        }
//        // TODO: Check name duplicates
//        if (dto.name != null) {
//            app.setName(dto.name);
//        }
//        app.setPrivate(dto.isPrivate);
//        DB.persist(app);
//        return app;
//    }
//
//    public ApplicationVersionDto createApplicationVersionDto(ApplicationVersion version) {
//        if (version == null) return null;
//        ApplicationVersionDto dto = new ApplicationVersionDto();
//        dto.id = KeyFactory.keyToString(version.getId());
//        dto.name = version.getName();
//        return dto;
//    }
//
//    public ApplicationVersion updateApplicationVersion(ApplicationVersionDto dto) {
//        ApplicationVersion version = getApplicationVersion(KeyFactory.stringToKey(dto.id));
//        if (version == null) {
//            return null;
//        }
//        // TODO: Check name duplicates
//        version.setName(dto.name);
//        DB.persist(version);
//        return version;
//    }
//
//    public ApplicationInstanceDto createApplicationInstanceDto(ApplicationInstance instance) {
//        if (instance == null) return null;
//        ApplicationInstanceDto dto = new ApplicationInstanceDto();
//        dto.id = KeyFactory.keyToString(instance.getId());
//        dto.name = instance.getName();
//        return dto;
//    }
//
//    public ApplicationInstance updateApplicationInstance(ApplicationInstanceDto dto) {
//        ApplicationInstance instance = getApplicationInstance(KeyFactory.stringToKey(dto.id));
//        if (instance == null) {
//            return null;
//        }
//        // TODO: Check name duplicates
//        instance.setName(dto.name);
//        DB.persist(instance);
//        return instance;
//    }

    public void deleteApplication(String applicationId) {
        Application application = getApplicationForEdit(KeyFactory.stringToKey(applicationId));
        application.delete();
    }

    public ApplicationGroup getApplicationGroupTx(Application application, ApplicationInstance applicationInstance,
                                                  ApplicationVersion applicationVersion) {
        checkViewApplication(application);
        DB.begin();
        try {
            return getApplicationGroupInternal(application.getId(), applicationInstance.getId(),
                    applicationVersion.getId());
        }
        finally {
            DB.commit();
        }
    }

    public ApplicationGroup getApplicationGroup(Application application, ApplicationInstance applicationInstance,
                                                  ApplicationVersion applicationVersion) {
        checkViewApplication(application);
        return getApplicationGroupInternal(application.getId(), applicationInstance.getId(), applicationVersion.getId());
    }

    public ApplicationGroup getApplicationGroupTx(Key applicationId, Key applicationInstanceId,
                                                  Key applicationVersionId) {
        // Authenticate application
        getApplication(applicationId);
        DB.begin();
        try {
            return getApplicationGroupInternal(applicationId, applicationInstanceId, applicationVersionId);
        }
        finally {
            DB.commit();
        }
    }

    public ApplicationGroup getApplicationGroup(Key applicationId, Key applicationInstanceId,
                                            Key applicationVersionId) {
        // Authenticate application
        getApplication(applicationId);
        return getApplicationGroupInternal(applicationId, applicationInstanceId, applicationVersionId);
    }

    public ApplicationGroup getApplicationGroup(Key applicationGroupId) {
        ApplicationGroup group = DB.find(ApplicationGroup.class, applicationGroupId);
        if (group != null) {
            // Authenticate application
            getApplication(group.getApplicationId());
        }
        return group;
    }

    // Gets or creates an application group. No security done here.
    private ApplicationGroup getApplicationGroupInternal(Key applicationId, Key applicationInstanceId,
                                                Key applicationVersionId) {
        ApplicationGroup group = DB.createDBQuery(ApplicationGroup.class)
                .addWhere("applicationId", applicationId)
                .addWhere("applicationInstanceId", applicationInstanceId)
                .addWhere("applicationVersionId", applicationVersionId)
                .getSingleResult();
        if (group == null) {
            group = new ApplicationGroup(applicationId, applicationInstanceId, applicationVersionId);
            DB.persist(group);
        }
        return group;
    }

    public List<ApplicationGroup> getApplicationGroupList(Key applicationKey, Key instanceKey, Key versionKey) {
        DBQuery<ApplicationGroup> q = DB.createDBQuery(ApplicationGroup.class)
                .addOrderBy("applicationId, applicationInstanceId, applicationVersionId");
        if (applicationKey != null) {
            q.addWhere("applicationId", applicationKey);
        }
        if (instanceKey != null) {
            q.addWhere("applicationInstanceId", instanceKey);
        }
        if (versionKey != null) {
            q.addWhere("applicationVersionId", versionKey);
        }
        List<ApplicationGroup> list = q.getResultList();
        List<ApplicationGroup> authorised = new ArrayList<ApplicationGroup>();
        for (ApplicationGroup group : list) {
            if (canViewApplication(group.getApplication())) {
                authorised.add(group);
            }
        }
        return authorised;
    }

//    public List<ApplicationGroupDto> getApplicationGroupDtoList(Key applicationKey, Key instanceKey, Key versionKey) {
//        List<ApplicationGroup> list = getApplicationGroupList(applicationKey, instanceKey, versionKey);
//        List<ApplicationGroupDto> dtos = new ArrayList<ApplicationGroupDto>();
//        for (ApplicationGroup group : list) {
//            dtos.add(createApplicationGroupDto(group));
//        }
//        return dtos;
//    }
//
//    private ApplicationGroupDto createApplicationGroupDto(ApplicationGroup group) {
//        ApplicationGroupDto dto = new ApplicationGroupDto();
//        dto.id = Long.toString(group.getId().getId());
//        dto.application = group.getApplication().getName();
//        dto.applicationInstance = group.getApplicationInstance().getName();
//        dto.applicationVersion = group.getApplicationVersion().getName();
//        return dto;
//    }

    public ApplicationUser addApplicationUser(Application app, Key userId) {
        checkApplicationForEdit(app);
        // Check for duplicates
        ApplicationUser user = getApplicationUser(app, userId);
        if (user == null) {
            user = new ApplicationUser(app, userId, RoleEnum.USER);
            app.addUser(user);
            DB.persist(user);
        }
        return user;
    }

    public ApplicationUser addApplicationUser(Application app, String email) {
        UserData user = userLogic.getUserByEmail(email);
        return addApplicationUser(app, user.getId());
    }

//    public ApplicationUserDto createApplicationUserDto(ApplicationUser appUser) {
//        ApplicationUserDto dto = new ApplicationUserDto();
//        dto.id = KeyFactory.keyToString(appUser.getId());
//        dto.applicationId = appUser.getApplication().keyToString();
//        dto.userId = KeyFactory.keyToString(appUser.getUserId());
//        return dto;
//    }

    public ApplicationUser getApplicationUser(Application app, Key userId) {
        // Check permissions
        getApplicationForEdit(app.getId());
        DBQuery<ApplicationUser> q = DB.createDBQuery(ApplicationUser.class)
                .addWhere("application", app)
                .addWhere("userId", userId);
        return q.getSingleResult();
    }

    public ApplicationUser getApplicationUser(Key applicationUserId) {
        // Check permissions
        ApplicationUser user = DB.find(ApplicationUser.class, applicationUserId);
        if (user != null) {
            checkApplicationForEdit(user.getApplication());
        }
        return user;
    }

    public void deleteApplicationUser(Key applicationUserId) {
        // Check permissions
        ApplicationUser user = DB.find(ApplicationUser.class, applicationUserId);
        if (user != null) {
            checkApplicationForEdit(user.getApplication());
            user.delete();
        }
    }

    public void deleteApplicationUser(Key appId, Key userId) {
        // Check permissions
        getApplicationForEdit(appId);
        DBQuery<ApplicationUser> q = DB.createDBQuery(ApplicationUser.class)
                .addWhere("applicationId", appId)
                .addWhere("userId", userId);
        q.delete();
    }

    public Application getApplicationByName(String applicationName) {
        Application app = DB.findByName(Application.class, applicationName);
        if (app != null) {
            checkViewApplication(app);
        }
        return app;
    }

    public ApplicationInstance getApplicationInstanceByName(Application app, String instanceName) {
        // Assume app has been authenticated
        return DB.createDBQuery(ApplicationInstance.class)
                .addWhere("application", app)
                .addWhere("name", instanceName)
                .getSingleResult();
    }

    public ApplicationVersion getApplicationVersionByName(Application app, String versionName) {
        // Assume app has been authenticated
        return DB.createDBQuery(ApplicationVersion.class)
                .addWhere("applicationId", app.getId())
                .addWhere("name", versionName)
                .getSingleResult();
    }
}
